﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;

using Noris.Schedule.Support.Services;
using System.Windows.Forms;
using System.Drawing;
using Noris.Schedule.Support.Core;

namespace Noris.Schedule.Support
{
	/// <summary>
	/// Hlavní správce podkladových dat aplikace.
	/// Používá se pro získání dat trvalého charakteru i pro volání systémových metod.
	/// </summary>
	/// <remarks>
	/// Obsahuje privátní singleton na svoji instanci, v níž uchovává trvalá data.
	/// Uchovává v sobě konfiguraci, reflexi výkonných tříd.
	/// </remarks>
	public partial class Steward
	{
		#region SYSTÉM FUNKCÍ - KONSTRUKCE
		/// <summary>
		/// Iniciace subsystému funkcí
		/// </summary>
		private void _FunctionMenuInit()
		{
			this._FunctionLockWrite = new object();
			this._FunctionGroupDict = new Dictionary<FunctionMenuItemKey, FunctionGroupCls>();
		}
		/// <summary>
		/// Ukončení života subsystému funkcí
		/// </summary>
		private void _FunctionMenuDispose()
		{
			this._FunctionGroupDict.Clear();
			this._FunctionLockWrite = null;
		}
		/// <summary>
		/// Pro každou situaci (situace je definovaná svým klíčem FunctionMenuItemKey)
		/// je zde uložen jeden záznam, který obsahuje soupis funkcí, které se mají v dané situaci zobrazovat.
		/// </summary>
		private Dictionary<FunctionMenuItemKey, FunctionGroupCls> _FunctionGroupDict;
		private object _FunctionLockWrite;
		#endregion
		#region VYHLEDÁNÍ SEZNAMU VHODNÝCH OBJEKTŮ PRO MENU PODLE DANÉ SITUACE
		/// <summary>
		/// Pro daný klíč najde a vrátí definici položek kontextového menu.
		/// Klíčem je typ grafu, třída řádku a dat a grafický typ elementu.
		/// Definice menu je soupis vhodných nabídek pro danou situaci.
		/// Vhodné nabídky systém vybírá z dostupných poskytovatelů služby typu IFunctionMenuItem, 
		/// a to pomocí jejich metody GetMenuSuitableFor().
		/// Při příštím požadavku na získání menu pro identickou situaci je seznam položek již vrácen z paměti.
		/// </summary>
		/// <param name="graphMode">Typ grafu</param>
		/// <param name="menuKind">Prostor, kde se kliklo</param>
		/// <param name="elementDrawType">Druh elementu</param>
		/// <param name="rowClassNumber">Číslo třídy řádku</param>
		/// <param name="elementClassNumber">Číslo třídy elementu</param>
		/// <returns></returns>
		private FunctionGroupCls _FunctionGetMenu(RowGraphMode graphMode, FunctionMenuItemAreaType menuKind, GraphElementShapeType elementShapeType, int rowClassNumber, int elementClassNumber)
		{
			FunctionMenuItemKey menuKey = new FunctionMenuItemKey(graphMode, menuKind, elementShapeType, rowClassNumber, elementClassNumber);
			return this._FunctionGetMenu(menuKey);
		}
		/// <summary>
		/// Pro daný klíč najde a vrátí definici položek kontextového menu.
		/// Klíčem je typ grafu, třída řádku a dat a grafický typ elementu.
		/// Definice menu je soupis vhodných nabídek pro danou situaci.
		/// Vhodné nabídky systém vybírá z dostupných poskytovatelů služby typu IFunctionMenuItem, 
		/// a to pomocí jejich metody GetMenuSuitableFor().
		/// Při příštím požadavku na získání menu pro identickou situaci je seznam položek již vrácen z paměti.
		/// </summary>
		/// <param name="menuKey">Klíč situace, pro kterou se hledá menu</param>
		/// <returns></returns>
		private FunctionGroupCls _FunctionGetMenu(FunctionMenuItemKey menuKey)
		{
			FunctionGroupCls value;
			// 1. Pokud už mám pro daný klíč nalezenou definici menu, rovnou ji vrátím:
			if (this._FunctionGroupDict.TryGetValue(menuKey, out value))
				return value;

			// 2. Pro daný klíč nevím, která skupina menu bude ta pravá - zeptám se všech funkcí, zda se chtějí zobrazit:
			lock (this._FunctionLockWrite)
			{
				if (this._FunctionGroupDict.TryGetValue(menuKey, out value)) return value;
				this._FunctionCreateMenuGroup(menuKey);
				if (this._FunctionGroupDict.TryGetValue(menuKey, out value)) return value;
			}
			return null;
		}
		/// <summary>
		/// Ze všech dostupných funkcí vybere ty, které jsou vhodné pro danou situaci, 
		/// a z nich sestavenou definici menu uloží do this._FunctionGroupDict
		/// </summary>
		/// <param name="menuKey">Klíč situace, pro kterou se hledá menu</param>
		private void _FunctionCreateMenuGroup(FunctionMenuItemKey menuKey)
		{
			IEnumerable<IFunctionMenuItem> functions = this._GetServiceObjectList<IFunctionMenuItem>(true);
			FunctionGroupCls menuItemList = new FunctionGroupCls(menuKey);
			foreach (IFunctionMenuItem function in functions)
			{
				if (function == null) continue;

				// Zeptáme se postupně každé funkce, zda chce být zobrazena v menu pro danou situaci:
				FunctionMenuItemSuitableArgs args = new FunctionMenuItemSuitableArgs(menuKey);
				bool isSuitable = function.IsFunctionSuitableFor(args);
				if (!isSuitable) continue;

				// Zařadíme funkci do menu:
				menuItemList.AddOneItem(function.GetType(), args);
			}
			// Zařadíme celé menu do paměti pro danou situaci:
			this._FunctionGroupDict.Add(menuKey, menuItemList);
		}
		#endregion
	}
	#region SUBCLASS FunctionGroupCls : SEZNAM VŠECH POLOŽEK MENU PRO KONKRÉTNÍ SITUACI
	/// <summary>
	/// FunctionGroupCls : třída pro uchování setříděného seznamu všech funkcí, které jsou vhodné pro jeden konkrétní případ 
	/// (pro jednu kombinaci FunctionMenuItemKey).
	/// Seznam funkcí je setříděný a dokáže ze sebe vygenerovat menu.
	/// </summary>
	public class FunctionGroupCls : IDisposable
	{
		#region KONSTRUKCE
		internal FunctionGroupCls(FunctionMenuItemKey menuKey)
		{
			this._MenuKey = menuKey;
			this._MenuItems = new List<ServiceMenuOneItem>();
		}
		private FunctionMenuItemKey _MenuKey;
		private List<ServiceMenuOneItem> _MenuItems;
		/// <summary>
		/// Příznak, že seznam není setříděn
		/// </summary>
		private bool _IsUnsorted;
		#endregion
		#region PROPERTY
		/// <summary>
		/// Klíč situace pro toto menu
		/// </summary>
		internal FunctionMenuItemKey MenuKey { get { return this._MenuKey; } }
		/// <summary>
		/// Položky menu = jednotlivé funkce
		/// </summary>
		internal List<ServiceMenuOneItem> MenuItems { get { this._SortIfNeed(); return this._MenuItems; } }
		#endregion
		#region METODY
		/// <summary>
		/// Do menu vloží jednu položku.
		/// </summary>
		/// <param name="functionType"></param>
		/// <param name="args"></param>
		internal void AddOneItem(Type functionType, FunctionMenuItemSuitableArgs args)
		{
			this._MenuItems.Add(new ServiceMenuOneItem(functionType, args));
			this._IsUnsorted = true;
		}
		/// <summary>
		/// Pokud je toho zapotřebí, setřídí položky v menu
		/// </summary>
		private void _SortIfNeed()
		{
			if (!this._IsUnsorted) return;
			this._MenuItems.Sort(ServiceMenuOneItem.CompareByOrder);
			this._IsUnsorted = false;
		}
		/// <summary>
		/// Dispose
		/// </summary>
		public void Dispose()
		{
			this._MenuItems.Clear();
		}
		/// <summary>
		/// Vytvoří ze svých dat a vrátí kontextové menu.
		/// Pokud si z něj uživatel vybere, je třeba odchytit event menu ItemClicked, ošetřit jej (vygenerovat argumenty pro běh)
		/// a předat řízení do metody this.ItemClicked(ToolStripItem toolItem, FunctionMenuItemRunArgs args)
		/// </summary>
		/// <returns></returns>
		public ContextMenuStrip GetContextMenuStrip(FunctionMenuItemRunArgs runArgs)
		{
			List<ServiceMenuOneItem> menuItems = this.MenuItems;
			if (menuItems == null || menuItems.Count == 0) return null;

			// Nejprve si zjistím, jaký stav Enabled bude pro jednotlivé MenuItems, a teprve až potom sestavím menu.
			// Důvod: občasné problémy s pamětí při současném provádění.
			foreach (ServiceMenuOneItem menuItem in menuItems)
				menuItem.DetectCurrentItemState(runArgs);

			// Kontextové menu:
			ContextMenuStrip menu = new ContextMenuStrip();
			menu.AutoClose = true;
			menu.DropShadowEnabled = true;
			// menu.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow;
			menu.RenderMode = ToolStripRenderMode.Professional;
			menu.ShowCheckMargin = false;
			menu.ShowImageMargin = true;
			menu.ShowItemToolTips = true;
			menu.Text = "Nabídka funkcí";

			// Titulek plus podtržení:
			ToolStripLabel label = new ToolStripLabel("Nabídka funkcí");
			label.AutoSize = true;
			label.DisplayStyle = ToolStripItemDisplayStyle.Text;
			menu.Items.Add(label);
			menu.Items.Add(new ToolStripSeparator());

			// Přidat položky (jsou setříděné), zajistit Separators:
			string lastGroup = null;
			for (int m = 0; m < menuItems.Count; m++)
			{
				ServiceMenuOneItem menuItem = menuItems[m];
				string currGroup = menuItem.MenuGroup;
				if (lastGroup != null && currGroup != lastGroup)
					menu.Items.Add(new ToolStripSeparator());

				menu.Items.Add(menuItem.GetToolStripItem(runArgs));
				lastGroup = currGroup;
			}
			return menu;
		}
		/// <summary>
		/// Někdo kliknul na položku menu
		/// </summary>
		/// <param name="e"></param>
		public void ItemClicked(ToolStripItem toolItem, FunctionMenuItemRunArgs args)
		{
			ServiceMenuOneItem.RunFunction(toolItem, args);
		}
		#endregion
	}
	#endregion
	#region SUBCLASS ServiceMenuOneItem : JEDNA POLOŽKA MENU
	/// <summary>
	/// ServiceMenuOneItem : jedna položka menu = jedna funkce
	/// </summary>
	internal class ServiceMenuOneItem
	{
		#region KONSTRUKCE
		internal ServiceMenuOneItem(Type functionType, FunctionMenuItemSuitableArgs args)
		{
			this._FunctionType = functionType;
			this._MenuItem = args.MenuItem;
			this._MenuCaption = args.MenuCaption;
			this._MenuImage = args.MenuImage;
			this._MenuToolTipText = args.MenuToolTipText;
			this._EnabledStateDependingOnElement = args.EnabledStateDependingOnElement;
			this._MenuHasSubItems = args.MenuHasSubItems;
			if (args.MenuSubItemsCaption != null)
				this._MenuSubItemsCaption = new Dictionary<string, string>(args.MenuSubItemsCaption);
			if (args.MenuSubItemsImage != null)
				this._MenuSubItemsImage = new Dictionary<string, Image>(args.MenuSubItemsImage);
			this._MenuGroup = args.MenuGroup;
			this._MenuOrder = args.MenuOrder;
		}
		public override string ToString()
		{
			return "{Function=" + this.MenuCaption + "; Code=" + this.FunctionType.FullName + "}";
		}
		private Type _FunctionType;
		private ToolStripItem _MenuItem;
		private string _MenuCaption;
		private Image _MenuImage;
		private string _MenuToolTipText;
		private bool _EnabledStateDependingOnElement;
		private bool _CurrentStateEnabled;
		private bool _MenuHasSubItems;
		private Dictionary<string, string> _MenuSubItemsCaption;
		private Dictionary<string, Image> _MenuSubItemsImage;
		private string _MenuGroup;
		private int _MenuOrder;
		#endregion
		#region PROPERTY
		/// <summary> { get; }
		/// Typ funkce
		/// </summary>
		internal Type FunctionType { get { return this._FunctionType; } }
		/// <summary> { get; set; }
		/// Kompletně sestavená položka menu.
		/// Pokud funkce chce, může jí sestavit včetně všech parametrů, a takto bude položka zobrazována.
		/// </summary>
		public ToolStripItem MenuItem { get { return this._MenuItem; } }
		/// <summary> { get; }
		/// Text menu
		/// </summary>
		public string MenuCaption { get { return this._MenuCaption; } }
		/// <summary> { get; }
		/// Obrázek v menu
		/// </summary>
		public Image MenuImage { get { return this._MenuImage; } }
		/// <summary> { get; set; }
		/// Text ToolTipu
		/// </summary>
		public string MenuToolTipText { get { return this._MenuToolTipText; } }
		/// <summary> { get; set; }
		/// Příznak, že položka menu může měnit svůj stav podle stavu elementu, na kterém je menu zavoláno.
		/// Pokud ano, pak před zobrazením menu je volána metoda IFunctionMenuItem.GetMenuItemEnabledFor() s předáním informací o aktuálním elementu.
		/// </summary>
		public bool EnabledStateDependingOnElement { get { return this._EnabledStateDependingOnElement; } }
		/// <summary> { get; set; }
		/// Příznak, že položka menu má mít sub-položky, viz this.MenuSubItemsCaption
		/// </summary>
		public bool MenuHasSubItems { get { return this._MenuHasSubItems; } }
		/// <summary> { get; set; }
		/// Text menu podpoložek menu.
		/// Klíč v Dictionary je klíčem podpoložky.
		/// Value v Dictionary je zobrazovaný text.
		/// Podpoložky mají Images v MenuSubItemsImage.
		/// </summary>
		public Dictionary<string, string> MenuSubItemsCaption { get { return this._MenuSubItemsCaption; } }
		/// <summary> { get; set; }
		/// Text menu podpoložek menu.
		/// Klíč v Dictionary je klíčem podpoložky.
		/// Value v Dictionary je Image pro položku menu.
		/// </summary>
		public Dictionary<string, Image> MenuSubItemsImage { get { return this._MenuSubItemsImage; } }
		/// <summary> { get; }
		/// Skupina menu. Pokud chceme mít svoje položky menu pohromadě, pak sem vložíme unikátní název skupiny. 
		/// Hodnotou MenuOrder zajistíme třídění položek v rámci skupiny.
		/// </summary>
		public string MenuGroup { get { return (!String.IsNullOrEmpty(this._MenuGroup) ? this._MenuGroup : String.Empty); } }
		/// <summary> { get; }
		/// Pořadí položky menu v rámci její skupiny MenuGroup.
		/// </summary>
		public int MenuOrder { get { return this._MenuOrder; } }
		#endregion
		#region COMPARER
		/// <summary>
		/// Porovná dvě položky menu podle pořadí, v němž mají být zobrazeny.
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		internal static int CompareByOrder(ServiceMenuOneItem a, ServiceMenuOneItem b)
		{
			int cmp = String.Compare(a.MenuGroup, b.MenuGroup);
			if (cmp != 0) return cmp;
			cmp = a.MenuOrder.CompareTo(b.MenuOrder);
			if (cmp != 0) return cmp;
			cmp = a.MenuCaption.CompareTo(b.MenuCaption);
			if (cmp != 0) return cmp;
			return String.Compare(a.FunctionType.FullName, b.FunctionType.FullName);
		}
		#endregion
		#region GENERÁTOR POLOŽKY MENU, SPOUŠTĚČ AKCE Z POLOŽKY MENU
		/// <summary>
		/// Položka menu si zjistí pro konkrétní situaci, zda bude položka menu aktivní.
		/// </summary>
		/// <param name="runArgs"></param>
		internal void DetectCurrentItemState(FunctionMenuItemRunArgs runArgs)
		{
			this._CurrentStateEnabled = true;
			if (this.EnabledStateDependingOnElement)
			{	// Stav menu je závislý na aktuálním stavu editoru => stav editoru je popsán v parametru 
				IFunctionMenuItem functionObject = Steward.GetServiceObject<IFunctionMenuItem>(this.FunctionType, true);
				if (functionObject != null)
					this._CurrentStateEnabled = functionObject.IsMenuItemEnabledFor(runArgs);
			}
		}
		/// <summary>
		/// Vygeneruje a vrátí položku menu.
		/// Data má uložená v sobě.
		/// </summary>
		/// <returns></returns>
		internal ToolStripItem GetToolStripItem(FunctionMenuItemRunArgs runArgs)
		{
			ToolStripItem item;
			if (this.MenuItem != null)
			{
				item = this.MenuItem;
			}
			else
			{
				item = new ToolStripMenuItem(this.MenuCaption, this.MenuImage);
				item.ToolTipText = this.MenuToolTipText;
			}

			item.Enabled = this._CurrentStateEnabled;
			item.Tag = this.FunctionType;
			return item;
		}
		/// <summary>
		/// Spustí funkci, která je navázaná na dané položce menu.
		/// Položka menu je vytvořena metodou GetToolStripItem(), a v jejím Tagu je uložen Type cílové funkce.
		/// Zdejší metoda je static, protože není k dispozici reference na vlastní položku ServiceMenuOneItem, která odpovídá této funkci,
		/// a zatím ani tuto položku nepotřebujeme. Stačí Type funkce.
		/// </summary>
		/// <param name="toolItem">Položka menu</param>
		/// <param name="args">Data pro spuštění funkce = obshaují informace o grafu, řádku a elementu odkud se funkce volá</param>
		internal static void RunFunction(ToolStripItem toolItem, FunctionMenuItemRunArgs args)
		{
			object tag = toolItem.Tag;
			if (tag == null || (!(tag is Type)))
				return;

			Type functionType = tag as Type;
			if (functionType == null) return;

			IFunctionMenuItem functionObject = Steward.ObjectGenerator<IFunctionMenuItem>(functionType);
			if (functionObject == null) return;

			try
			{
				// Tady by to chtělo spustit nový Thread, v něm spustit funkci, a current thread dát uspat do konce běhu funkce,
				// aby vizuální vrstva nebyla mrtvá, ale živá a čekající.
				functionObject.Run(args);
			}
			catch (Exception exc)
			{	// Odchytím i System výjimky, a zobrazím je.
				// To proto, že volaná funkce může chybovat jakkoli, ale nemá to shodit celé okno plánovací tabule.
				Dialog.ShowError(exc, MessageInfo.Get("Při běhu funkce došlo k chybě:"));
			}
		}
		#endregion
	}
	#endregion
}
